<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <title>PyRobot Web UI</title>
    <style type="text/css">
      body { font-size: small; }
      table.sensors {
        border: none;
        border-collapse: collapse;
        font-size: smaller;
      }
      table.sensors tr:hover { background-color: #ddd; }
      table.sensors th { text-align: left; }
      table.sensors td { padding: 0px 2em 0px 0px; }
      div.logging_pane {
        border: solid 1px black;
        width: 400px;
        height: 240px;
        overflow: auto;
        padding: 0.25em;
      }
      p.logging {
        font-size: smaller;
        margin: 0px;
      }
    </style>
    <script type="text/javascript" src="/static/MochiKit/MochiKit.js"></script>
    <script type="text/javascript">
      var SENSOR_DELAY = 2;
      var SENSOR_HISTORY = 25;
      var sensors = {};

      function Sparkline(data) {
        var w = 100;
        var h = 8;
        var attrs = {
          'style': 'display: inline; height: ' + h + 'px; width: ' + w + 'px;',
          'height': h + 'px',
          'width': w + 'px',
        }
        var canvas = createDOM('canvas', attrs);
        var min = Math.min.apply(Math, data);
        var max = Math.max.apply(Math, data);
        if (max == min) {
          max++;  // Prevents a divide-by-zero error.
        }

        var c = canvas.getContext('2d');
        c.strokeStyle = 'gray';
        c.lineWidth = 1;
        c.beginPath();

        for (var i = 0; i < data.length; i++) {
          var x = (w / SENSOR_HISTORY) * i;
          var y = h - (((data[i] - min) / (max - min)) * h);
          c.lineTo(x, y);
        }
        c.stroke();
        return canvas;
      }

      buildSensorTable = function(obj) {
        var rows = [];
        for (attr in obj) {
          var graph = DIV();
          if (typeof(sensors[attr]) != 'undefined' &&
              sensors[attr].length > 1 &&
              typeof(sensors[attr][0]) == 'number') {
            graph = Sparkline(sensors[attr]);
          }
          rows.push([attr, obj[attr], graph]);
        }
        rows.sort(function(a, b) { return ((a[0] < b[0]) ? -1 : 1); });
        row_display = function (row) {
          return TR({'id': row[0]}, map(partial(TD, null), row));
        }
        var newTable = TABLE({'id': 'sensors', 'class': 'sensors'},
          TH({'width': '100'}, 'Sensors'), TH(), TH(),
          TBODY(null, map(row_display, rows)));
        return newTable;
      }

      updateSensorData = function() {
        log('Update sensors.')
        var d = MochiKit.Async.loadJSONDoc('/sensors');
        d.addCallback(function(r) {
          for (attr in r) {
            if (typeof(sensors[attr]) == 'undefined') {
              sensors[attr] = [];
            }
            sensors[attr].push(r[attr]);
            if (sensors[attr].length > SENSOR_HISTORY) {
              sensors[attr].shift();
            }
          }
          new_sensor_table = buildSensorTable(r);
          sensor_table = getElement('sensors');
          MochiKit.DOM.swapDOM(sensor_table, new_sensor_table);
          for (attr in r) {
            if (r[attr] != sensors[attr][sensors[attr].length - 2]) {
              MochiKit.Visual.Highlight(attr);
            }
          }
          MochiKit.Async.callLater(SENSOR_DELAY, updateSensorData);
        });
        d.addErrback(function(e) {
          logError(e);
          MochiKit.Async.callLater(SENSOR_DELAY, updateSensorData);
        });
      }

      comet = function() {
        var d = MochiKit.Async.loadJSONDoc('/comet');
        d.addCallback(function(r) {
          pushData(r.key, r.value);
          comet();
        });
        d.addErrback(function(e) {
          log('Comet error.');
          comet();
        });
      }

      pushData = function(key, value) {
        if (key == 'logging') {
          log('Updating logging pane.');
          logging_pane = getElement('logging_pane');
          MochiKit.DOM.appendChildNodes(logging_pane,
              P({'class': 'logging'}, value))
          logging_pane.scrollTop = logging_pane.scrollHeight;
        }
      }

      MochiKit.Signal.connect(window, 'onload', updateSensorData);
      MochiKit.Signal.connect(window, 'onload', comet);
    </script>
    <script type="text/javascript">
      var pan_max = 70;
      var tilt_max = 30;
      var img_width = 320;
      var img_height = 240;

      // NOTE(damonkohler): These values were found by trial-and-error.
      var pixel_pan = pan_max / 400;
      var pixel_tilt = tilt_max / 150;

      var pan = 0;
      var tilt = 0;

      panAndTilt = function(x, y) {
        if (typeof(x) != 'undefined') {
          pan = x;
        }
        if (typeof(y) != 'undefined') {
          tilt = y;
        }
        if (pan > 0 && pan > pan_max) {
          pan = pan_max;
        } else if (pan < 0 && pan < -pan_max) {
          pan = -pan_max;
        }
        if (tilt > 0 && tilt > tilt_max) {
          tilt = tilt_max;
        } else if (tilt < 0 && tilt < -tilt_max) {
          tilt = -tilt_max;
        }
        pan = Math.round(pan);
        tilt = Math.round(tilt);
        MochiKit.Async.loadJSONDoc('/track?x=' + pan + '&y=' + tilt);
      }

      panAndTilt();

      clicked = function(e) {
        var x = e.mouse().client.x - elementPosition('webcam').x;
        var y = e.mouse().client.y - elementPosition('webcam').y;
        log(x, y);
        x = x - (img_width / 2);
        y = (img_height / 2) - y;
        log(x, y);
        pan += x * pixel_pan;
        tilt += y * pixel_tilt;
        panAndTilt();
      }

      rearView = function() {
        MochiKit.Async.loadJSONDoc('/rearview');
        var webcam = getElement('webcam');
        MochiKit.Async.callLater(8, function() {
          webcam.src = ('http://damonkohler.dnsalias.net:8081/?' +
              (new Date()).getTime());
        });
      }

      frontView = function() {
        MochiKit.Async.loadJSONDoc('/frontview');
        var webcam = getElement('webcam');
        MochiKit.Async.callLater(8, function() {
          webcam.src = ('http://damonkohler.dnsalias.net:8081/?' +
              (new Date()).getTime());
        });
      }
    </script>
  </head>
  <body>
    <table>
      <tr>
        <td width="320" height="240">
          <img id="webcam" src="http://damonkohler.dnsalias.net:8081"
              width="320" height="240">
          <script>
            MochiKit.Signal.connect('webcam', 'onclick', clicked);
          </script>
        </td>
        <td valign="top">
          Light <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/light_on');
               return false;">On</a> /
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/light_off');
               return false;">Off</a><br>
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/speak?msgs=' +
               prompt('What would you like to say?', 'I come in peace.'));
               return false;">Talk</a><br>
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/restart');
               return false;">Don't Panic</a><br>
          <br>Camera<br>
          <a href="#"
             onclick="javascript:panAndTilt(0, 0); return false;">Center</a><br>
          <a href="#"
             onclick="javascript:panAndTilt(-70, 0); return false;">Left</a><br>
           <a href="#"
             onclick="javascript:panAndTilt(70, 0); return false;">Right</a><br>
           <a href="#"
             onclick="javascript:panAndTilt(0, 40); return false;">Up</a><br>
           <a href="#"
             onclick="javascript:panAndTilt(0, -40); return false;">Down</a><br>
           <br>
          View <a href="#"
             onclick="javascript:frontView(); return false;">Front</a> /
           <a href="#"
             onclick="javascript:rearView(); return false;">Rear</a><br>
            </td>
        <td width="25"></td>
        <td valign="top" rowspan="4">
          <div id="sensors"></div>
        </td>
      </tr>
      <tr>
        <td align="center">
          <a href="#"
            onclick="javascript:MochiKit.Async.loadJSONDoc('/forward');
              return false;">Forward</a> |
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/reverse');
               return false;">Reverse</a> |
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/left');
               return false;">Left</a> |
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/right');
               return false;">Right</a> |
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/dock');
               return false;">Dock</a> |
          <a href="#"
             onclick="javascript:MochiKit.Async.loadJSONDoc('/undock');
               return false;">Undock</a>
          </td>
          <td></td>
      </tr>
      <tr>
        <td valign="top" colspan="3">
          <div id="logging_pane" class="logging_pane">
            <p class="logging">Welcome to Fido! W00f!</p>
          </div>
        </td>
      </tr>
      <tr><td></td></tr>
    </table>
  </body>
</html>
